﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace LaoLuo
{
    /// <summary>
    /// Provides a set of static methods for use with enums
    /// </summary>
    public static class EnumHelper
    {
        private static bool IsDefinedInEnum(Type enumType, object value, Type underlyingType)
        {
            var v = value;

            if (v.GetType().IsNumeric())
            {
                v = Convert.ChangeType(value, underlyingType);
            }

            return Enum.IsDefined(enumType, v);
        }

        /// <summary>
        /// Determines whether the specified <paramref name="value"/> is defined in <typeparamref name="TEnum"/>.
        /// It's better than <see cref="Enum.IsDefined"/>, 'cause <code>Enum.IsDefined(typeof(DayOfWeek), (byte)1)</code> will throw an ArgumentException for DayOfWeek's underlying-type is Int32
        /// </summary>
        /// <typeparam name="TEnum">type of enum to check</typeparam>
        /// <param name="value">object to check</param>
        /// <returns>check result</returns>
        public static bool IsDefinedInEnum<TEnum>(object value)
            where TEnum : struct, IComparable, IFormattable, IConvertible
        {
            return IsDefinedInEnum(typeof(TEnum), value, EnumInternals<TEnum>.UnderlyingType);
        }

        /// <summary>
        /// Determines whether the specified <paramref name="value"/> is defined in <typeparamref name="TEnum"/>.
        /// It's better than <see cref="Enum.IsDefined"/>, 'cause <code>Enum.IsDefined(typeof(DayOfWeek), (byte)1)</code> will throw an ArgumentException for DayOfWeek's underlying-type is Int32
        /// </summary>
        /// <param name="enumType">type of enum to check</param>
        /// <param name="value">object to check</param>
        /// <returns>check result</returns>
        public static bool IsDefinedInEnum(Type enumType, object value)
        {
            return IsDefinedInEnum(enumType, value, Enum.GetUnderlyingType(enumType));
        }

        /// <summary>
        /// 尝试获取枚举值, 如果失败则返回null, <paramref name="value"/>必须在枚举中定义
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <param name="ignoreCase"> </param>
        /// <returns></returns>
        public static T? TryParse<T>(object value, bool ignoreCase = true) //, bool valueMustBeDefinedInEnum = true
            where T : struct, IComparable, IFormattable, IConvertible
        {
            T t;

            if (value != null && Enum.TryParse(value.ToString(), ignoreCase, out t))
            {
                if (IsDefinedInEnum<T>(t))
                {
                    return t; 
                }
            }

            return null;
        }

        /// <summary>
        /// 获取枚举值
        /// </summary>
        /// <param name="value"></param>
        /// <param name="ignoreCase"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        public static T Parse<T>(object value, bool ignoreCase = true)
            where T : struct, IComparable, IFormattable, IConvertible
        {
            var ret = TryParse<T>(value, ignoreCase);

            if (ret.HasValue) return ret.Value;

            throw new ArgumentException();
        }

        /// <summary>
        /// 获取枚举值
        /// </summary>
        /// <param name="value"></param>
        /// <param name="defaultValue"> </param>
        /// <param name="ignoreCase"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        public static T Parse<T>(object value, T defaultValue, bool ignoreCase = true)
            where T : struct, IComparable, IFormattable, IConvertible
        {
            var ret = TryParse<T>(value, ignoreCase);

            return ret.HasValue ? ret.Value : defaultValue;
        }

        /// <summary>
        /// 获取枚举的所有值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        /// <exception cref="TypeArgumentException"></exception>
        public static IList<T> GetAllEnumValues<T>()
            where T : struct, IComparable, IFormattable, IConvertible
        {
            ThrowHelper.ThrowIfNotEnumType<T>();

            return EnumInternals<T>.Values;
        }
    }
}
